<template>
  <a-form
    ref="formRef"
    :model="formState"
    :rules="state.rules"
    :label-col="labelCol"
    :wrapper-col="wrapperCol"
  >
    <a-row>
      <a-col
        :span="item.span || parseInt(24 / columnsCount)"
        v-for="(item, k) in formItems"
        :key="item.name"
      >
        <a-form-item
          v-if="
            isFn(item.hidden)
              ? !item.hidden(formState, formItems, item)
              : !item.hidden
          "
          :label="item.label"
          :name="`${item.name}`"
          :wrapperCol="{ span: 24 }"
          style="display: flex"
        >
          <template v-if="item.slot">
            <slot
              :name="`${item.slot}`"
              :item="item"
              :formState="formState"
            ></slot>
          </template>
          <template v-else>
            <component
              v-if="item.type != 'switch'"
              :is="
                CUSTOM_ITEMS.includes(item.type) ? item.type : `a-${item.type}`
              "
              v-model:value="formState[item.name]"
              v-bind="{
                placeholder:
                  item.type == 'range-picker'
                    ? ['开始时间', '结束时间']
                    : `${item.type == 'input' ? '请输入' : '请选择'}${
                        item.label
                      }`,
                ...item.itemProps,
              }"
              allowClear
              :style="
                (item.itemProps && item.itemProps.style) || { width: '300px' }
              "
              @change="(...args) => formChange(item, ...args)"
              :disabled="!!item.itemProps?.disabled || !!preview"
            />
            <a-switch
              v-else
              v-model:checked="formState[item.name]"
              v-bind="item.itemProps"
              @change="(...args) => formChange(item, ...args)"
            ></a-switch>
          </template>
        </a-form-item>
      </a-col>
    </a-row>
    <a-form-item
      :wrapper-col="handleSubmitWrapperCol()"
      v-if="needSubmitButton"
    >
      <div
        :style="
          columnsCount == 1
            ? { justifyContent: 'flex-start' }
            : { justifyContent: 'center' }
        "
        class="form-submit-btn"
      >
        <slot
          name="submit-btn"
          :onSubmit="onSubmit"
          :resetForm="resetForm"
          :formState="formState"
        >
          <a-button type="primary" @click="() => onSubmit()" v-if="!preview">
            提交
          </a-button>
          <!-- <a-button style="margin-left: 10px" @click="resetForm">重置</a-button> -->
        </slot>
      </div>
    </a-form-item>
  </a-form>
</template>
<script>
  import { defineComponent, reactive, ref, toRaw, watch } from 'vue';
  import { Form } from 'ant-design-vue';
  const useForm = Form.useForm;
  const customItems = ['Uploader', 'VideoUploader', 'FileUploader', 'RichText'];

  const SELECT_PROPS = {
    showSearch: true,
    optionFilterProp: 'label',
    filterOption: (input, option) => {
      return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
    },
  };

  function isFn(fn) {
    return Object.prototype.toString.call(fn) == '[object Function]';
  }

  function formItems2FormState(formItems) {
    let fs = {};
    for (let v of formItems) {
      fs[v.name] = customItems.includes(v.type) ? v.value || [] : v.value;
      if (v.type == 'RichText') {
        fs[v.name] = v.value;
      }
    }
    return fs;
  }

  function handleParams(params, formItems) {
    let newParams = {};
    for (let [k, v] of Object.entries(params)) {
      let curr = formItems.find((m) => m.name == k);
      if (curr) {
        switch (curr.type) {
          case 'date-picker':
            if (!v) break;
            if (curr.valueType == 'number') {
              newParams[k] = parseInt(v.valueOf() / 1000);
            } else {
              newParams[k] = v.format(curr.itemProps.format);
            }
            break;
          case 'time-picker':
            if (!v) break;
            if (curr.valueType == 'number') {
              newParams[k] = parseInt(v.valueOf() / 1000);
            } else {
              newParams[k] = v.format(curr.itemProps.format);
            }
            break;
          case 'range-picker':
            if (!v) break;
            if (curr.valueType == 'number') {
              newParams[curr.valueFields[0]] =
                v[0] && parseInt(v[0].valueOf() / 1000);
              newParams[curr.valueFields[1]] =
                v[1] && parseInt(v[1].valueOf() / 1000);
            } else {
              newParams[curr.valueFields[0]] =
                v[0] && v[0].format(curr.itemProps.format);
              newParams[curr.valueFields[1]] =
                v[1] && v[1].format(curr.itemProps.format);
            }
            break;
          default:
            newParams[k] = params[k];
        }
      }
    }
    return { ...params, ...newParams };
  }

  function feildStr2Obj(data, dataField) {
    let val = data;
    if (!data.list) return [];
    if (dataField) {
      if (dataField.indexOf('.') == -1) {
        val = data[dataField];
      } else {
        let dataFields = dataField.split('.').reverse();

        while (dataFields.length > 0) {
          let col = dataFields.pop();
          val = val[col];
        }
      }
    }

    return val;
  }

  async function handleAsyncOptions(formItems, formState, asyncItemName, emit) {
    for (let item of formItems) {
      if (asyncItemName && asyncItemName != item.name) {
        continue;
      }
      item.type = item.type || 'select';
      if (!item.itemProps) {
        item.itemProps = {
          ...SELECT_PROPS,
        };
      } else {
        item.itemProps = {
          ...SELECT_PROPS,
          ...item.itemProps,
        };
      }
      if (item.asyncOptions && !item.asyncOptions.notFirstFetch) {
        let res = {};
        try {
          res = await item.asyncOptions.api(
            item.asyncOptions.apiParams
              ? item.asyncOptions.apiParams(item, formState, formItems)
              : {}
          );
        } catch (err) {}

        let data = res.data;
        if (!data || !data.list) {
          item.itemProps.treeData = item.itemProps.options = [];
        }
        if (item.asyncOptions.dataField) {
          data = feildStr2Obj(data, item.asyncOptions.dataField);
        }
        emit('optionsAsync', item, data);

        if (item.asyncOptions.handleData) {
          item.itemProps.treeData = item.itemProps.options =
            item.asyncOptions.handleData(data);
        } else {
          if (item.asyncOptions.optionFields) {
            item.itemProps.treeData = item.itemProps.options = data.map(
              (m) => ({
                ...m,
                label: m[item.asyncOptions.optionFields.label],
                title: m[item.asyncOptions.optionFields.label],
                key: m[item.asyncOptions.optionFields.value],
                value: m[item.asyncOptions.optionFields.value],
              })
            );
          } else {
            console.warn('缺少asyncOptions.handleData');
          }
        }
      } else {
        if (item.asyncOptions) {
          item.asyncOptions.notFirstFetch = false;
        }
      }
    }
  }

  export default defineComponent({
    name: 'FormContent',
    emits: ['formChange', 'afterInfo', 'optionsAsync', 'get:formState'],
    props: {
      rules: {
        type: Object,
        default: {},
      },
      formItems: {
        type: Array,
        default: [],
      },
      preview: {
        type: Boolean,
        default: false,
      },
      handleInitFormData: {
        type: [Function, undefined],
        default: undefined,
      },
      beforeSubmit: {
        type: [Function, undefined],
        default: undefined,
      },
      afterSubmit: {
        type: [Function, undefined],
        default: undefined,
      },
      submitApi: {
        type: [Function, undefined],
        default: undefined,
      },
      needSubmitButton: {
        type: Boolean,
        default: true,
      },
      columnsCount: {
        type: Number,
        default: 1,
      },
      formStateInit: {
        type: Object,
        default: {},
      },
      formItemsAfterInfo: {
        type: [Function, undefined],
        default: undefined,
      },
      infoInit: {
        type: Boolean,
        default: false,
      },
    },
    methods: {
      handleSwitchVal(val) {
        return ![0, false].includes(val) ? true : false;
      },

      handleSubmitWrapperCol() {
        return this.columnsCount == 1 ? { span: 14, offset: 4 } : { span: 24 };
      },
    },
    setup(props, context) {
      const {
        rules,
        formItems,
        handleInitFormData,
        beforeSubmit,
        afterSubmit,
        submitApi,
        formStateInit,
        formItemsAfterInfo,
      } = props;

      const formRef = ref(null);

      const CUSTOM_ITEMS = ref(customItems);

      const formState = reactive(
        handleInitFormData
          ? handleInitFormData(formItems2FormState(formItems))
          : formItems2FormState(formItems)
      );

      // 对异步options做处理
      handleAsyncOptions(formItems, formState, undefined, context.emit);

      const state = reactive({
        api: submitApi,
        hiddenCb: formItems.map((m) =>
          isFn(m.hidden)
            ? (...args) => {
                if (args[2].name != m.name) {
                  let isHidden = m.hidden(...args, m);
                  if (isHidden) {
                    delete formState[m.name];
                  } else if (!formState[m.name]) {
                    formState[m.name] = m.value;
                  }
                }
              }
            : null
        ),
        rules,
      });

      // 获取required的item
      let requiredItemsRules = {};

      formItems.forEach((item) => {
        if (item.required) {
          requiredItemsRules[item.name] = [
            {
              required: true,
              message: `${item.type == 'input' ? '请输入' : '请选择'}${
                item.label
              }`,
            },
          ];
        }
      });

      state.rules = { ...requiredItemsRules, ...state.rules };

      watch(
        () => props.rules,
        (val) => {
          state.rules = { ...state.rule, ...val };
        }
      );

      const setFormState = (col, value) => {
        formState[col] = value;
      };

      const onSubmit = (cb) => {
        formRef.value
          .validate()
          .then(() => {
            let params = handleParams(toRaw(formState), toRaw(formItems));
            params = beforeSubmit
              ? beforeSubmit(params, formItems)
              : toRaw(params);
            if (!params) {
              console.error('beforeSubmit没有return值');
              return;
            }
            cb
              ? cb(params)
              : state.api(params).then((res) => {
                  afterSubmit && afterSubmit(res);
                });
          })
          .catch((error) => {
            console.warn('error', error);
          });
      };

      const resetForm = () => {
        formRef.value.resetFields();
      };

      const formChange = (item, ...args) => {
        state.hiddenCb.forEach((cb, k) => {
          cb && cb(formState, formItems, item);
        });
        if (customItems.includes(item.type)) {
          formState[item.name] = args[0];
        }
        if (item.relateName instanceof Array && item.relateName.length > 0) {
          let relateName = [...item.relateName];
          relateName.forEach((name, index) => {
            if (index == 0) {
              handleAsyncOptions(formItems, formState, name, context.emit);
            }
            formState[name] = undefined;
            let isset = formItems.find((m) => m.name == name);
            if (
              isset &&
              (isset.itemProps.mode == 'multiple' ||
                isset.itemProps.treeCheckable == true)
            ) {
              formState[name] = [];
            }
          });
          relateName.shift();
          formItems.forEach((m) => {
            if (relateName.includes(m.name)) {
              m.itemProps.options = [];
            }
          });
        }
        context.emit('formChange', item, formState, formItems, args);
      };

      // 编辑进来初始化数据
      const handleInitAsyncInfo = () => {
        setTimeout(() => {
          formItems.forEach((item) => {
            if (
              item.relateName instanceof Array &&
              item.relateName.length > 0
            ) {
              item.relateName.forEach((name) => {
                handleAsyncOptions(formItems, formState, name, context.emit);
              });
            }
          });
          context.emit('afterInfo', formState, formItems);
        });
      };

      const info2FormState = (info) => {
        for (let [k, item] of Object.entries(formState)) {
          formState[k] = info[k];
        }
        formItemsAfterInfo && formItemsAfterInfo(formItems);
        handleInitAsyncInfo();
        context.emit('get:formState', formState);
      };
      if (props.infoInit) {
        info2FormState(props.formStateInit);
      }

      watch(
        () => formState,
        (val) => {
          context.emit('get:formState', val);
        }
      );

      // 监听info初始化
      watch(
        () => [props.formStateInit],
        ([newFormStateInit], [prevFormStateInit]) => {
          if (
            !props.formStateInit ||
            Object.keys(props.formStateInit).length == 0
          ) {
            return;
          }
          info2FormState(newFormStateInit);
        }
      );

      watch(
        () => props.submitApi,
        (val) => {
          state.api = val;
        }
      );

      return {
        labelCol: {
          span: 4,
        },
        wrapperCol: {
          span: 14,
        },
        formState,
        onSubmit,
        resetForm,
        formChange,
        state,
        CUSTOM_ITEMS,
        setFormState,
        isFn,
        formRef,
      };
    },
  });
</script>

<style lang="scss">
  .form-submit-btn {
    display: flex;
  }
</style>
